package app

import (
	"context"
	"energy-amiot-platform/core"
	"energy-amiot-platform/model/dbspo"
	"energy-amiot-platform/model/request"
	"energy-amiot-platform/model/response"
	"energy-amiot-platform/repository"
	"energy-amiot-platform/utils"
	"energy-amiot-platform/utils/hash"
	"energy-amiot-platform/utils/respout"
	"errors"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/exp/rand"
	"gorm.io/gorm"
)

var (
	allowstrchar = []rune("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~=+%^*/()[]{}/!@#$?|")
	moduleCode   = 1
)

// 此接口不允许客户端调用，需要另外认证
// @Tags 应用管理
// @Summary 新建一个应用
// @Description 新建一个应用
// @Accept  json
// @Product json
// @Param   input body request.AppCreateReq true					"入参"
// @Success 200 {object} response.Response{result=dbspo.App} 	"Ok"
// @Router /app/{id} [post]
func create(ctx *gin.Context) {
	var req request.AppCreateReq
	var handleCode = 1
	err := ctx.BindJSON(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	core.DATABASE.Cur().Transaction(func(tx *gorm.DB) error {
		tx1, b := repository.APP.ExistWithCompanyIdAndAppName(tx, req.Cid, req.Name)
		switch {
		case tx1.Error != nil:
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, tx.Error, nil)
			return tx1.Error
		case b == true:
			err = errors.New("应用已存在")
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, err, nil)
			return err
		}

		var dpo dbspo.App
		dpo.Cid = req.Cid
		dpo.Name = req.Name
		key, secret := generateAppSecretKeyPair()
		dpo.AppKey, dpo.Secret = key, secret
		tx1 = repository.APP.Create(tx, &dpo)
		if tx1.Error != nil {
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 3, respout.StrFail, tx.Error, nil)
			return tx1.Error
		}

		secretHash := generateAppSecretHash(key, secret)
		err = core.REDIS.Client.Set(context.Background(), "mqtt_app:"+secretHash, req.Cid, 0).Err()
		if err != nil {
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 4, respout.StrFail, err, nil)
			return err
		}

		respout.Ok(ctx, &dpo)
		return nil
	})
}

// @Tags 应用管理
// @Summary 获取指定标识的应用
// @Description 获取指定标识的应用
// @Product json
// @Param   id path int true		"应用标识"
// @Success 200 {object} response.Response{result=dbspo.App} "Ok"
// @Router /app/{id} [get]
func findByKey(ctx *gin.Context) {
	var handleCode = 2
	id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	var result dbspo.App
	err = core.DATABASE.Cur().First(&result, "id=?", id).Error
	switch {
	case err == gorm.ErrRecordNotFound:
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err, nil)
		return
	case err != nil:
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, err, nil)
		return
	}

	result.Secret = "*"
	respout.Ok(ctx, result)
}

// @Tags 应用管理
// @Summary 获取应用集
// @Description 获取应用集
// @Product json
// @Param   cid query int true		"企业名称"
// @Param   name query string false		"应用名称"
// @Param   pageSize query int false		"每页数量" default(10)
// @Param   pageNum query int false		"第几页" default(1)
// @Success 200 {object} response.Response{result=response.PageData{list=[]dbspo.App}} "Ok"
// @Router /app [get]
func pagingQuery(ctx *gin.Context) {
	var handleCode = 2
	var req request.AppPagingQueryReq
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	if req.PageNum == 0 {
		req.PageNum = 1
	}

	if req.PageSize == 0 {
		req.PageSize = 10
	}

	var count int64
	var result []dbspo.App
	db := core.DATABASE.Cur()
	if err = db.Model(&dbspo.App{}).Where("cid=?", req.Cid).Count(&count).Error; err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err, nil)
		return
	}

	offset := req.PageSize * (req.PageNum - 1)
	err = db.Model(&dbspo.App{}).Where("cid=?", req.Cid).Offset(offset).Limit(req.PageSize).Scan(&result).Error
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, err, nil)
		return
	}

	respout.Ok(ctx, response.PageData{
		Total: count,
		List:  result,
	})
}

// @Tags 应用管理
// @Summary 删除应用
// @Description 删除应用
// @Product json
// @Param 	id 		path 	string 	true 	"应用标识"
// @Success 200 {object} response.Response "Ok"
// @Router /app/{id} [delete]
func deleteByKey(ctx *gin.Context) {
	var handleCode = 3
	id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	core.DATABASE.Cur().Transaction(func(tx *gorm.DB) error {
		var count int64
		tx1, app := repository.APP.FindByKey(tx, id)
		switch {
		case tx.Error == gorm.ErrRecordNotFound:
			err2 := errors.New("未找到设备")
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err2, nil)
			return err2
		case tx.Error != nil:
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, tx.Error, nil)
			return tx.Error
		case count > 0:
			err2 := errors.New("该应用下有设备")
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 3, respout.StrFail, err2, nil)
			return err2
		}

		tx1 = repository.APP.Delete(tx, app)
		switch {
		case tx1.Error != nil:
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 4, respout.StrFail, tx.Error, nil)
			return tx1.Error
		case tx1.RowsAffected == 0:
			err2 := errors.New("应用不存在")
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 5, respout.StrFail, err2, nil)
			return err2
		}

		secretHash := generateAppSecretHash(app.AppKey, app.Secret)
		err = core.REDIS.Client.Del(context.Background(), "mqtt_app:"+secretHash).Err()
		if err != nil {
			respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 6, respout.StrFail, err, nil)
			return err
		}

		respout.Ok(ctx, nil)
		return nil
	})
}

func generateAppSecretKeyPair() (string, string) {
	source := rand.NewSource(uint64(time.Now().UnixNano()))
	r := rand.New(source)
	v := r.Int()%5 + 1
	key, _ := utils.RandStringBytesCrypto(7 + v)
	secret, _ := utils.RandStringBytesCrypto(7 + v)

	return key, secret
}

// 只在使用时生成
func generateAppSecretHash(key, secret string) string {
	return hash.GenerateAppSecretHash(key, secret, "hkfg+")
}
